﻿// This software is the confidential and proprietary information of NetBay.
// Such confidential information should not be disclosed.
// NetBay copyright(c) 2012

#region

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NetBay.Core.BusinessEntities;
using NetBay.Core.BusinessEntities.Workflow;
using NetBay.Core.Log;
using Rio.Flow.Business.Repository;
using Rio.Workflow.Runtime.Enums;
using Rio.Workflow.Runtime.Events;
using Rio.Workflow.Runtime.Exceptions;
using Rio.Workflow.Runtime.Interop;

#endregion

namespace Rio.Workflow.Runtime
{
    public sealed class WorkflowEngine
    {
        #region Readonly & Static Fields

        private readonly WorkerQueue _actionWorker;
        private readonly object _locker = new object();
        private readonly bool _useWorkerQueue;
        private readonly KeyedByIdentifierCollection _wkfActionTypeProvider;
        private readonly KeyedByTypeNameCollection _wkfTypeProvider;

        #endregion

        #region Fields

        private RuntimeState _state;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="WorkflowEngine" /> class.
        /// </summary>
        public WorkflowEngine()
        {
            LoadedModels = new List<WorkflowModel>();
            _state = RuntimeState.Stopped;
            _wkfTypeProvider = new KeyedByTypeNameCollection();
            _wkfActionTypeProvider = new KeyedByIdentifierCollection();
            _actionWorker = new WorkerQueue();
            _useWorkerQueue = false;
        }

        #endregion

        #region Instance Properties

        /// <summary>
        /// Indicating whether if the runtime is started
        /// </summary>
        public bool IsStarted
        {
            get
            {
                if (_state == RuntimeState.Started)
                    return true;
                return false;
            }
        }

        /// <summary>
        /// Gets or sets the loaded models.
        /// </summary>
        /// <value>The loaded models.</value>
        public List<WorkflowModel> LoadedModels { get; set; }

        /// <summary>
        /// Gets the runtime state.
        /// </summary>
        /// <value>The state.</value>
        public RuntimeState State
        {
            get { return _state; }
        }

        #endregion

        #region Instance Methods

        /// <summary>
        /// Executes the action.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        public ActionResult ExecuteAction(ActionRequest request)
        {
            lock (_locker)
            {
                if (!IsStarted)
                {
                    StartRuntime();
                }
                LogManager.Instance().LogDebug("WorkflowRuntime - ExecuteAction()...");

                using (WorkflowContextBase instance = PrepareActionContext(request))
                {
                    //Executes action
                    ActionResult result = ActionResult.NotSupported;
                    if (_useWorkerQueue)
                    {
                        _actionWorker.AddAction(instance, GetActionExecutor(instance.CurrentAction), request);
                    }
                    else
                    {
                        instance.RaiseBeforeExecuteAction();
                        result = instance.ExecuteAction(GetActionExecutor(instance.CurrentAction), request);
                        instance.RaiseAfterExecuteAction();
                    }
                    return result;
                }
            }
        }

        /// <summary>
        /// Requests the chrono.
        /// </summary>
        /// <param name="wkfDefinition">The WKF definition.</param>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        public Item RequestChrono(WorkflowDefinition wkfDefinition, ChronoRequest request)
        {
            if (!IsStarted)
            {
                StartRuntime();
            }
            LogManager.Instance().LogDebugFormat("WorkflowRuntime - RequestChrono => " + wkfDefinition);
            using (WorkflowContextBase instance = GetWorkflowContext(wkfDefinition))
            {
                instance.RaiseBeforeChronoRequest();
                Item chrono = instance.RequestChrono(request);
                instance.RaiseAfterChronoRequest(chrono);

                return chrono;
            }
        }

        /// <summary>
        /// Starts the runtime.
        /// </summary>
        public void StartRuntime()
        {
            if (State != RuntimeState.Started)
            {
                try
                {
                    LogManager.Instance().LogInfo(Message.WorkflowRuntimeStarting);

                    //Get all definitions and assemblies
                    LogManager.Instance().LogDebug(Message.GettingModels);
                    LoadedModels = WorkflowModelRepository.GetWorkflowModel();
                    if (LoadedModels.Count == 0)
                    {
                        LogManager.Instance().LogWarnFormat(Message.NoModelsFounded);
                    }

                    foreach (WorkflowModel model in LoadedModels)
                    {
                        //Load assembly
                        Assembly assembly = WorkflowActivator.LoadAssembly(model.AssemblyName);
                        if (assembly == null)
                        {
                            throw new MissingAssemblyException(string.Format("The assembly {0} could not be loaded!",
                                                                             model.AssemblyName));
                        }
                        LogManager.Instance().LogDebugFormat("- The assembly:'{0}' for the model:{1} is loaded",
                                                             assembly.ToString(), model.Identifier);

                        //Get type in the assembly
                        string typeName = model.GetFullTypeName();
                        Type worktype = assembly.GetType(typeName);
                        if (worktype == null)
                        {
                            throw new MissingAssemblyException(string.Format("The type {0} could not be found!",
                                                                             typeName));
                        }
                        LogManager.Instance().LogDebugFormat("Workflow model type founded: '{0}'", worktype.ToString());

                        //Store model type
                        if (!_wkfTypeProvider.ContainsKey(typeName))
                            _wkfTypeProvider.Add(typeName, worktype);
                    }

                    //Loads each assemblies actions 
                    List<WorkflowAction> actionsToLoad = WorkflowModelRepository.GetAllActions();
                    foreach (var action in actionsToLoad)
                    {
                        //Load assembly
                        Assembly actionAssembly = WorkflowActivator.LoadAssembly(action.AssemblyName);
                        if (actionAssembly == null)
                        {
                            throw new MissingAssemblyException(
                                string.Format("The action assembly {0} could not be loaded!", action.AssemblyName));
                        }
                        LogManager.Instance().LogDebugFormat("- The action assembly:'{0}'", actionAssembly.ToString());

                        //Get type in the assembly
                        string actionTypeName = action.GetFullTypeName();
                        Type actionType = actionAssembly.GetType(actionTypeName);
                        if (actionType == null)
                        {
                            throw new MissingAssemblyException(string.Format("The type {0} could not be found!",
                                                                             actionTypeName));
                        }
                        if (actionType.GetInterface(typeof (IActionExecutor).FullName) != null)
                        {
                            LogManager.Instance().LogDebugFormat("WorkflowAction type founded: '{0}'",
                                                                 actionType.ToString());

                            //Store action type
                            if (!_wkfActionTypeProvider.ContainsKey(action))
                                _wkfActionTypeProvider.Add(action, actionType);
                        }
                        else
                        {
                            LogManager.Instance().LogErrorFormat(
                                "WorkflowAction type: '{0}' does not implement IActionExecutor interface!",
                                actionTypeName);
                        }
                    }


                    LogManager.Instance().LogInfo(Message.WorkflowRuntimeStarted);
                    _state = RuntimeState.Started;
                    RaiseRuntimeStarted();
                }
                catch (Exception ex)
                {
                    LogManager.Instance().LogError("WorkflowRuntime could not be started, exception:" + ex.Message);
                    throw;
                }
            }
        }

        /// <summary>
        /// Starts the workflow.
        /// </summary>
        /// <param name="wkfDefinition">The WKF definition.</param>
        /// <param name="context">The context.</param>
        /// <returns></returns>
        public bool StartWorkflow(WorkflowDefinition wkfDefinition, CreationContext context)
        {
            if (!IsStarted)
            {
                StartRuntime();
            }
            LogManager.Instance().LogDebugFormat("WorkflowRuntime - StartWorkflow => " + wkfDefinition);
            using (WorkflowContextBase wkfContext = GetWorkflowContext(wkfDefinition))
            {
                wkfContext.InitializeCreationContext(wkfDefinition, context, 0, 0);

                //Check if the wkf can start
                if (wkfContext.WorkflowCanStart(wkfDefinition, context))
                {
                    wkfContext.RaiseWorkflowStarting();

                    bool result = false;
                    WorkflowInstance wkfInstance = wkfContext.StartWorkflow(wkfDefinition, context);
                    if (wkfInstance != null)
                    {
                        wkfContext.RaiseWorkflowStarted();
                        result = true;
                    }
                    else
                    {
                        wkfContext.RaiseWorkflowFaulted();
                    }

                    return result;
                }
                else
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// Starts the workflow2.
        /// </summary>
        /// <param name="wkfDefinition">The WKF definition.</param>
        /// <param name="context">The context.</param>
        /// <returns></returns>
        public WorkflowInstance StartWorkflow2(WorkflowDefinition wkfDefinition, CreationContext context)
        {
            if (!IsStarted)
            {
                StartRuntime();
            }
            LogManager.Instance().LogDebugFormat("WorkflowRuntime - StartWorkflow2 => " + wkfDefinition);

            try
            {
                using (WorkflowContextBase wkfContext = GetWorkflowContext(wkfDefinition))
                {
                    wkfContext.InitializeCreationContext(wkfDefinition, context, 0, 0);

                    //Check if the wkf can start
                    if (wkfContext.WorkflowCanStart(wkfDefinition, context))
                    {
                        wkfContext.RaiseWorkflowStarting();
                        WorkflowInstance wkfInstance = wkfContext.StartWorkflow(wkfDefinition, context);
                        if (wkfInstance != null)
                        {
                            wkfContext.RaiseWorkflowStarted();
                        }
                        else
                        {
                            wkfContext.RaiseWorkflowFaulted();
                        }

                        return wkfInstance;
                    }
                    return null;
                }
            }
            catch (WorkflowEngineException ex)
            {
                LogManager.Instance().LogError(ex.ToString());
                return null;
            }
            catch(Exception ex1)
            {
                LogManager.Instance().LogError(ex1.ToString());
                throw new Exception("WorkflowRuntime - StartWorkflow2 Exception", ex1);
            }
        }

        /// <summary>
        /// Stops runtime
        /// </summary>
        public void StopRuntime()
        {
            LogManager.Instance().LogInfo(Message.WorkflowRuntimeStopping);
            _wkfActionTypeProvider.Clear();
            _wkfTypeProvider.Clear();
            LoadedModels.Clear();
            _state = RuntimeState.Stopped;
            RaiseRuntimeStopped();
            LogManager.Instance().LogInfo(Message.WorkflowRuntimeStopped);
        }

        /// <summary>
        /// Workflows Opened
        /// </summary>
        /// <param name="wkfDefinition">The WKF definition.</param>
        public void WorkflowOpened(WorkflowDefinition wkfDefinition)
        {
            if (!IsStarted)
            {
                StartRuntime();
            }
            LogManager.Instance().LogDebugFormat("WorkflowRuntime - WorkflowOpened => " + wkfDefinition);
            using (WorkflowContextBase instance = GetWorkflowContext(wkfDefinition))
            {
                instance.WorkflowOpened();
            }
        }

        /// <summary>
        /// Gets the action executor.
        /// </summary>
        /// <param name="wkfAction">The WKF action.</param>
        /// <returns></returns>
        private IActionExecutor GetActionExecutor(WorkflowAction wkfAction)
        {
            Type tWorkflow = _wkfActionTypeProvider[wkfAction.ActionOrConditionId];
            IActionExecutor instance = WorkflowFactory.GetInstance<IActionExecutor>(tWorkflow);
            return instance;
        }

        /// <summary>
        /// Gets the workflow context.
        /// </summary>
        /// <param name="wkfDefinition">The WKF definition.</param>
        /// <returns></returns>
        private WorkflowContextBase GetWorkflowContext(WorkflowDefinition wkfDefinition)
        {
            Type tWorkflow = _wkfTypeProvider[wkfDefinition.GetFullTypeName()];
            WorkflowContext instance = (WorkflowContext) WorkflowFactory.GetInstance(tWorkflow);
            if (instance == null)
            {
                throw new NullReferenceException(tWorkflow.ToString());
            }
            return instance;
        }

        /// <summary>
        /// Prepares the action context.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        private WorkflowContextBase PrepareActionContext(ActionRequest request)
        {
            //Get the instance
            WorkflowInstance wkfInstance = WorkflowInstanceRepository.GetWorkflowInstance(request.wkfInstanceId,
                                                                                          request.userId);
            if (wkfInstance == null)
                throw new NullReferenceException(
                    "PrepareActionContext - Cannot get the wkfInstance for the definition: " + request.wkfInstanceId);

            //Get the definition
            WorkflowDefinition wkfDefinition =
                WorkflowDefinitionRepository.GetWorkflowDefinitionById(wkfInstance.DefinitionId);
            if (wkfDefinition == null)
                throw new NullReferenceException(
                    "PrepareActionContext - Cannot get the wkfDefinition for the definition: " +
                    wkfInstance.DefinitionId);

            //Get document state 
            List<WorkflowDocument> documents = null;
            if (request.wkfDocumentStateIds != null)
                documents = wkfInstance.WorkItems.OfType<WorkflowDocument>().ToList();

            //Get the action
            WorkflowAction wkfAction = _wkfActionTypeProvider.GetWorkflowAction(request.wkfActionId);
            if (wkfAction == null)
                throw new NullReferenceException(
                    "PrepareActionContext - Cannot get the wkfAction for the definition: " + wkfInstance.DefinitionId);

            //Create and initialize the context instance
            WorkflowContextBase instance = GetWorkflowContext(wkfDefinition);
            if (instance == null)
                throw new NullReferenceException(
                    "PrepareActionContext - Cannot create the context instance for the definition: " +
                    wkfInstance.DefinitionId);

            instance.InitializeActionContext(wkfDefinition, wkfInstance, wkfAction, documents, request.rguId,
                                             request.userId);
            return instance;
        }

        private void RaiseRuntimeStarted()
        {
            if (RuntimeStarted != null)
                RuntimeStarted(this, new WorkflowRuntimeEventArgs(RuntimeState.Stopped));
        }

        private void RaiseRuntimeStopped()
        {
            if (RuntimeStopped != null)
                RuntimeStopped(this, new WorkflowRuntimeEventArgs(RuntimeState.Stopped));
        }

        #endregion

        #region Event Declarations

        public event WorkflowRuntimeEventHandler RuntimeStarted;
        public event WorkflowRuntimeEventHandler RuntimeStopped;

        #endregion
    }
}